using System;
using System.Collections.Generic;
using System.Text;
using PandoraCommon;

namespace SessionReportGenerator
{
    /// <summary>
    /// Represents an interval of time with an initial speed and final speed value.
    /// </summary>
    public class SpeedInterval
    {
        private bool insideInterval = false;
        private bool isAccelerating = false;
        private GpsPosition previousPosition = null;
        private DateTime enterTime = DateTime.MinValue;
        private DateTime exitTime = DateTime.MaxValue;


        /// <summary>
        /// Initializes a new instance of the <see cref="SpeedInterval"/> class.
        /// </summary>
        /// <param name="initialSpeed">The initial speed.</param>
        /// <param name="finalSpeed">The final speed.</param>
        public SpeedInterval(double initialSpeed, double finalSpeed)
        {
            this.initialSpeed = initialSpeed;
            this.finalSpeed = finalSpeed;
        }


        #region InitialSpeed
        private double initialSpeed;

        /// <summary>
        /// Gets the initial speed.
        /// </summary>
        /// <value>The initial speed.</value>
        public double InitialSpeed
        {
            get { return initialSpeed; }
        } 
        #endregion

        #region FinalSpeed
        private double finalSpeed;

        /// <summary>
        /// Gets the final speed.
        /// </summary>
        /// <value>The final speed.</value>
        public double FinalSpeed
        {
            get { return finalSpeed; }
        } 
        #endregion

        #region BestTime
        private double bestTime = double.MaxValue;

        /// <summary>
        /// Gets the best time taken to accelerate/deccelerate through this speed interval.
        /// </summary>
        /// <value>The best time.</value>
        public double BestTime
        {
            get { return bestTime; }
        } 
        #endregion

        /// <summary>
        /// Processes a new position for this speed interval.
        /// </summary>
        /// <param name="newPosition">The new position.</param>
        public void ProcessPosition(GpsPosition newPosition)
        {

            if (this.initialSpeed < this.finalSpeed)
            {
                #region Acceleration Interval

                if (newPosition.Speed > initialSpeed && newPosition.Speed < finalSpeed) // If speed is inside interval
                {
                    if (!insideInterval && previousPosition != null) // If just entered speed interval, calculate enter time
                    {
                        if (previousPosition.Speed < newPosition.Speed)
                            this.isAccelerating = true;
                        else
                            this.isAccelerating = false;

                        if (isAccelerating)
                            enterTime = GetApproximateTimeAtSpeed(this.initialSpeed, newPosition, this.previousPosition);

                        insideInterval = true;
                    }
                }
                else
                {
                    if (newPosition.Speed > finalSpeed && insideInterval) // If just exited speed interval, calculate exit time
                    {
                        exitTime = GetApproximateTimeAtSpeed(this.finalSpeed, newPosition, this.previousPosition);

                        // Determine best time for this speed interval
                        TimeSpan timeGap = exitTime.Subtract(enterTime);
                        if (this.bestTime > timeGap.TotalMilliseconds)
                            this.bestTime = timeGap.TotalMilliseconds;
                    }

                    insideInterval = false;
                }

                #endregion
            }
            else
            {
                #region Braking Interval

                if (newPosition.Speed < initialSpeed && newPosition.Speed > finalSpeed) // If speed is inside interval
                {
                    if (!insideInterval && previousPosition != null) // If just entered speed interval, calculate enter time
                    {
                        if (previousPosition.Speed < newPosition.Speed)
                            this.isAccelerating = true;
                        else
                            this.isAccelerating = false;

                        if (!isAccelerating)
                            enterTime = GetApproximateTimeAtSpeed(this.initialSpeed, newPosition, this.previousPosition);

                        insideInterval = true;
                    }
                }
                else
                {
                    if (newPosition.Speed < initialSpeed && insideInterval) // If just exited speed interval, calculate exit time
                    {
                        exitTime = GetApproximateTimeAtSpeed(this.finalSpeed, newPosition, this.previousPosition);

                        // Determine best time for this speed interval
                        TimeSpan timeGap = exitTime.Subtract(enterTime);
                        if (this.bestTime > timeGap.TotalMilliseconds)
                            this.bestTime = timeGap.TotalMilliseconds;
                    }

                    insideInterval = false;
                }

                #endregion
            }

            this.previousPosition = newPosition;
        }

        /// <summary>
        /// Determines whether the specified current speed is inside this speed interval.
        /// </summary>
        /// <param name="currentSpeed">The specified current speed.</param>
        /// <returns>
        /// 	<c>true</c> if it is inside interval; otherwise, <c>false</c>.
        /// </returns>
        public bool IsInsideInterval(double currentSpeed)
        {
            return (currentSpeed < finalSpeed && currentSpeed > initialSpeed);
        }

        /// <summary>
        /// Gets the approximate time at the specified milestone speed.
        /// </summary>
        /// <param name="milestoneSpeed">The milestone speed.</param>
        /// <param name="currentPosition">The current position.</param>
        /// <param name="previousPosition">The previous position.</param>
        /// <returns></returns>
        public static DateTime GetApproximateTimeAtSpeed(double milestoneSpeed, GpsPosition currentPosition, GpsPosition previousPosition)
        {
            if ((milestoneSpeed > currentPosition.Speed && milestoneSpeed > previousPosition.Speed) ||
                (milestoneSpeed < currentPosition.Speed && milestoneSpeed < previousPosition.Speed) ||
                (milestoneSpeed == currentPosition.Speed && milestoneSpeed == previousPosition.Speed))
                throw new IncorrectSpeedIntervalException("Milestone speed it outside speed interval values");

            double speedDelta = 0;
            double timeDelta = 0;
            double deltaRatio = 0;
            DateTime resultTime = DateTime.MinValue;

            if (currentPosition.Speed > milestoneSpeed && milestoneSpeed > previousPosition.Speed) 
            {
                speedDelta = currentPosition.Speed - previousPosition.Speed;
                deltaRatio = (milestoneSpeed - previousPosition.Speed) / speedDelta;
            }
            else 
            {
                speedDelta = previousPosition.Speed - currentPosition.Speed;
                deltaRatio = (previousPosition.Speed - milestoneSpeed) / speedDelta;
            }

            timeDelta = currentPosition.TimeSince(previousPosition);
            resultTime = previousPosition.Date.AddMilliseconds(timeDelta * deltaRatio);

            return resultTime;

        }
    }
}
